import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from nicegui import ui, run
import logging
import os
from pathlib import Path
import time

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class DataAnalyzer:
    def __init__(self, raw_data_path='ds_salaries.csv', cleaned_data_path='cleaned_ds_salaries.csv'):
        self.raw_data_path = Path(raw_data_path)
        self.cleaned_data_path = Path(cleaned_data_path)
        self.raw_df = None
        self.cleaned_df = None
        self.visualizations = {}
        
    def load_data(self):
        """加载原始数据"""
        logger.info(f"1. 加载原始数据: {self.raw_data_path}")
        if not self.raw_data_path.exists():
            raise FileNotFoundError(f"原始数据文件不存在: {self.raw_data_path}")
        
        try:
            self.raw_df = pd.read_csv(self.raw_data_path)
            logger.info(f"成功加载原始数据，共 {len(self.raw_df)} 条记录")
            return self.raw_df
        except Exception as e:
            logger.error(f"加载数据失败: {str(e)}")
            raise
    
    def clean_data(self):
        """数据清洗与转换"""
        if self.raw_df is None:
            raise ValueError("请先加载原始数据")
            
        logger.info("2. 执行数据清洗...")
        self.cleaned_df = self.raw_df.copy()
        
        # 处理缺失值
        initial_rows = len(self.cleaned_df)
        self.cleaned_df.dropna(inplace=True)
        if len(self.cleaned_df) < initial_rows:
            logger.info(f"删除了 {initial_rows - len(self.cleaned_df)} 条包含缺失值的记录")
        
        # 数据类型转换
        if 'work_year' in self.cleaned_df.columns:
            self.cleaned_df['work_year'] = self.cleaned_df['work_year'].astype(int)
        
        # 处理薪资相关列
        salary_cols = ['salary', 'salary_in_usd']
        for col in salary_cols:
            if col in self.cleaned_df.columns:
                self.cleaned_df[col] = pd.to_numeric(self.cleaned_df[col], errors='coerce')
                self.cleaned_df.dropna(subset=[col], inplace=True)
        
        # 移除重复记录
        self.cleaned_df.drop_duplicates(inplace=True)
        
        logger.info(f"数据清洗完成，剩余 {len(self.cleaned_df)} 条记录")
        return self.cleaned_df
    
    def save_cleaned_data(self):
        """保存清洗后的数据到CSV文件"""
        if self.cleaned_df is None:
            raise ValueError("请先清洗数据")
            
        try:
            self.cleaned_df.to_csv(self.cleaned_data_path, index=False)
            logger.info(f"3. 清洗后的数据已保存至: {self.cleaned_data_path}")
            return True
        except Exception as e:
            logger.error(f"保存清洗后的数据失败: {str(e)}")
            raise
    
    def analyze_data(self):
        """执行数据分析，生成关键统计信息"""
        if self.cleaned_df is None:
            raise ValueError("请先清洗数据")
            
        logger.info("4. 执行数据分析...")
        analysis_results = {}
        
        # 基本统计信息
        analysis_results['basic_stats'] = self.cleaned_df.describe().to_dict()
        
        # 按经验水平分组的薪资统计
        exp_levels = ['EN', 'MI', 'SE', 'EX']  # 初级、中级、高级、专家
        analysis_results['exp_salary_stats'] = {}
        
        for exp in exp_levels:
            exp_data = self.cleaned_df[self.cleaned_df['experience_level'] == exp]
            if not exp_data.empty:
                analysis_results['exp_salary_stats'][exp] = {
                    'count': len(exp_data),
                    'avg_salary': exp_data['salary_in_usd'].mean(),
                    'median_salary': exp_data['salary_in_usd'].median(),
                    'min_salary': exp_data['salary_in_usd'].min(),
                    'max_salary': exp_data['salary_in_usd'].max()
                }
        
        # 按职位类型的薪资统计
        top_jobs = self.cleaned_df['job_title'].value_counts().head(10).index
        analysis_results['job_salary_stats'] = {
            job: self.cleaned_df[self.cleaned_df['job_title'] == job]['salary_in_usd'].mean()
            for job in top_jobs
        }
        
        # 按年份的薪资趋势
        analysis_results['yearly_trend'] = {
            year: self.cleaned_df[self.cleaned_df['work_year'] == year]['salary_in_usd'].mean()
            for year in self.cleaned_df['work_year'].unique()
        }
        
        logger.info("数据分析完成")
        return analysis_results
    
    def create_visualizations(self):
        """创建交互式可视化图表"""
        if self.cleaned_df is None:
            raise ValueError("请先清洗数据")
            
        logger.info("5. 创建可视化图表...")
        
        # 1. 薪资分布直方图
        fig1 = px.histogram(
            self.cleaned_df,
            x='salary_in_usd',
            nbins=30,
            title='薪资分布直方图 (USD)',
            labels={'salary_in_usd': '薪资 (USD)', 'count': '职位数量'},
            color_discrete_sequence=['#2c7fb8']
        )
        fig1.update_layout(showlegend=False)
        self.visualizations['salary_distribution'] = fig1
        
        # 2. 不同经验水平的薪资箱线图
        fig2 = px.box(
            self.cleaned_df,
            x='experience_level',
            y='salary_in_usd',
            title='不同经验水平的薪资分布',
            labels={'experience_level': '经验水平', 'salary_in_usd': '薪资 (USD)'},
            category_orders={'experience_level': ['EN', 'MI', 'SE', 'EX']},
            color='experience_level',
            color_discrete_map={'EN': '#a1caf1', 'MI': '#7393b3', 'SE': '#4a6fa5', 'EX': '#1a4d8f'}
        )
        self.visualizations['exp_salary_box'] = fig2
        
        # 3. 热门职位薪资条形图
        top_jobs = self.cleaned_df['job_title'].value_counts().head(10).index
        job_data = self.cleaned_df[self.cleaned_df['job_title'].isin(top_jobs)]
        
        fig3 = px.bar(
            job_data,
            x='job_title',
            y='salary_in_usd',
            title='热门职位平均薪资',
            labels={'job_title': '职位名称', 'salary_in_usd': '平均薪资 (USD)'},
            color='job_title',
            color_discrete_sequence=px.colors.qualitative.D3,
            hover_data=['job_title', 'salary_in_usd']
        )
        fig3.update_layout(xaxis_tickangle=-45, showlegend=False)
        self.visualizations['top_job_salaries'] = fig3
        
        logger.info("可视化图表创建完成")
        return self.visualizations

def create_web_interface(analyzer):
    """创建NiceGUI Web界面"""
    ui.markdown("# 数据科学薪资数据分析平台")
    ui.markdown("## 数据科学职位薪资分析")
    
    # 数据概览卡片
    with ui.grid(columns=2).classes('w-full'):
        with ui.card():
            ui.markdown(f"**原始数据路径**:\n{analyzer.raw_data_path}")
            ui.markdown(f"**清洗后数据路径**:\n{analyzer.cleaned_data_path}")
            ui.markdown(f"**数据记录数**:\n{len(analyzer.cleaned_df)}")
            ui.markdown(f"**数据列数**:\n{len(analyzer.cleaned_df.columns)}")
        
        with ui.card():
            ui.markdown("**数据字段说明**:")
            ui.markdown("- work_year: 工作年份")
            ui.markdown("- experience_level: 经验水平 (EN:初级, MI:中级, SE:高级, EX:专家)")
            ui.markdown("- job_title: 职位名称")
            ui.markdown("- salary: 薪资")
            ui.markdown("- salary_in_usd: 美元薪资")
            ui.markdown("- company_size: 公司规模 (S:小型, M:中型, L:大型)")
    
    # 可视化图表区域
    ui.markdown("## 交互式可视化图表")
    
    with ui.tabs().classes('w-full'):
        with ui.tab('薪资分布'):
            ui.plotly(analyzer.visualizations['salary_distribution']).classes('w-full h-80')
        
        with ui.tab('经验水平薪资对比'):
            ui.plotly(analyzer.visualizations['exp_salary_box']).classes('w-full h-80')
        
        with ui.tab('热门职位薪资'):
            ui.plotly(analyzer.visualizations['top_job_salaries']).classes('w-full h-80')
    
    # 经验水平筛选分析
    ui.markdown("## 经验水平薪资详情")
    exp_levels = {'初级 (EN)': 'EN', '中级 (MI)': 'MI', '高级 (SE)': 'SE', '专家 (EX)': 'EX'}
    reverse_exp_levels = {v: k for k, v in exp_levels.items()}
    selected_exp = ui.select(exp_levels, value='初级 (EN)', label='选择经验水平')
    
    exp_stats_card = ui.card().classes('w-full')
    with exp_stats_card:
        exp_data = analyzer.cleaned_df[analyzer.cleaned_df['experience_level'] == exp_levels[selected_exp.value]]
        ui.markdown(f"### {selected_exp.value} 薪资统计")
        with ui.grid(columns=3).classes('w-full'):
            with ui.card():
                ui.markdown(f"**平均薪资**\n${exp_data['salary_in_usd'].mean():,.2f}")
            with ui.card():
                ui.markdown(f"**中位数薪资**\n${exp_data['salary_in_usd'].median():,.2f}")
            with ui.card():
                ui.markdown(f"**数据记录数**\n{len(exp_data)}")
        
        # 该经验水平下的职位薪资分布
        job_salary = exp_data.groupby('job_title')['salary_in_usd'].mean().sort_values(ascending=False).head(8)
        fig = px.bar(
            x=job_salary.values,
            y=job_salary.index,
            orientation='h',
            title=f'{selected_exp.value} 职位薪资分布',
            labels={'x': '平均薪资 (USD)', 'y': '职位名称'},
            color_discrete_sequence=['#4a6fa5']
        )
        ui.plotly(fig).classes('w-full h-60')
    
    # 更新经验水平筛选时的图表
    def update_exp_stats():
        exp_data = analyzer.cleaned_df[analyzer.cleaned_df['experience_level'] == exp_levels[selected_exp.value]]
        with exp_stats_card:
            exp_stats_card.clear()
            ui.markdown(f"### {reverse_exp_levels.get(selected_exp.value, '初级 (EN)')} 薪资统计")
            with ui.grid(columns=3).classes('w-full'):
                with ui.card():
                    ui.markdown(f"**平均薪资**\n${exp_data['salary_in_usd'].mean():,.2f}")
                with ui.card():
                    ui.markdown(f"**中位数薪资**\n${exp_data['salary_in_usd'].median():,.2f}")
                with ui.card():
                    ui.markdown(f"**数据记录数**\n{len(exp_data)}")
            
            job_salary = exp_data.groupby('job_title')['salary_in_usd'].mean().sort_values(ascending=False).head(8)
            fig = px.bar(
                x=job_salary.values,
                y=job_salary.index,
                orientation='h',
                title=f'{selected_exp.value} 职位薪资分布',
                labels={'x': '平均薪资 (USD)', 'y': '职位名称'},
                color_discrete_sequence=['#4a6fa5']
            )
            ui.plotly(fig).classes('w-full h-60')
    
    selected_exp.on_value_change(update_exp_stats)

def main():
    try:
        # 初始化数据分析器
        analyzer = DataAnalyzer(raw_data_path='ds_salaries .csv')
        
        # 执行数据处理流程
        analyzer.load_data()
        analyzer.clean_data()
        analyzer.save_cleaned_data()
        analyzer.analyze_data()
        analyzer.create_visualizations()
        
        # 创建Web界面
        create_web_interface(analyzer)
        
        logger.info("6. 启动Web界面...")
        # 启动NiceGUI服务器
        ui.run(title='数据科学薪资分析平台', port=8080)
        
    except Exception as e:
        logger.error(f"应用程序错误: {str(e)}", exc_info=True)
        ui.notify(f"应用程序错误: {str(e)}", type='negative')
        raise

if __name__ in {'__main__', '__mp_main__'}:
    main()